/*
 * XApp.java, created on Oct 20, 2005
 * 
 * Copyright (c) 2005 - 2007
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xfc;

import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xfc.components.XFrame;
import org.xfc.components.XProxyAction;
import org.xfc.dialog.XErrorDialog;
import org.xfc.eventbus.XEventBus;
import org.xfc.util.XResources;
import org.xfc.util.XUtils;
import org.xfc.util.platform.XPlatform;
import org.xfc.util.reflect.XReflectClass;

// TODO: app lifecycle
// 	-init
//  -running
//  -shutting down
//      and some states in between?

// TODO: support for background initialization? Or should this be the responsibility of the app?

// TODO: is there some way of crawling the XFactory and xml resource file to construct UIs?

// TODO: have a property, shutdown on last window close -

// TODO: look into a class to abstract out the differences between mac / windows


/**
 * 
 * 
 * @author Devon Carew
 */
public class XApp
{
	private static Log log = LogFactory.getLog(XApp.class);
	
	private static XApp	app;
	
	
	private XEventBus 			eventBus = new XEventBus();
	
	private XServiceLocator		serviceLocator = new XServiceLocator();
	
	private XResources			resources = new XResources(this);
	
	private List				frames = new ArrayList();
	
	private Preferences			appPrefs;
	
	private String				appName;
	private ImageIcon			appIcon;
	
	private boolean				exitOnLastWindowClose = true;
	
	
	/**
	 * @return the global singleton instance of XApp
	 */
	public static final XApp getApp()
	{
		return app;
	}
	
	/**
	 * Set the global singleton instance of XApp.
	 *  
	 * @param inApp the global singleton instance of XApp
	 */
	protected static final void setApp(XApp inApp)
	{
		app = inApp;
	}
	
	/**
	 * @param appName
	 */
	public XApp(String appName)
	{
		// Initialize the XApp singleton instance.
		if (getApp() == null)
			setApp(this);
		
        setAppName(appName);
         
        platformInit(this);
        
		// Register an implementation of the component factory.
		getServiceLocator().registerService(XComponentFactory.class, new XDefaultComponentFactory(this));
		
		// Register our I18N strings.
		getResources().addInternationalizationResources(ResourceBundle.getBundle(XApp.class.getName()));
		
		setupLookAndFeel();
		
		// TODO: is this too early in the application startup to do this?
		restoreState(getAppPreferences());
	}
	
	/**
	 * @return the active XAppFrame
	 */
	public XAppFrame getActiveAppFrame()
	{
		// TODO: better algorithim, when a frame is activated bubble it to the top.
		
		if (frames.size() == 0)
			return null;
		
		return (XAppFrame)frames.get(0);
	}
	
	/* singletons*/
	
	/**
	 * @return the eventbus for this application
	 */
	public XEventBus getEventBus()
	{
		return eventBus;
	}
	
	/**
	 * @return Returns the factory.
	 */
	public static XComponentFactory getComponentFactory()
	{
		return (XComponentFactory)getApp().getService(XComponentFactory.class);
	}
	
	/**
	 * @param serviceInterface
	 * @return the service for the given interface
	 */
	public Object getService(Class serviceInterface)
	{
		return getServiceLocator().getService(serviceInterface);
	}
	
	/**
	 * @return Returns the serviceLocator.
	 */
	public XServiceLocator getServiceLocator()
	{
		return serviceLocator;
	}

	/**
	 * @param serviceLocator The serviceLocator to set.
	 */
	public void setServiceLocator(XServiceLocator serviceLocator)
	{
		this.serviceLocator = serviceLocator;
	}
	
	/**
	 * @return Returns the appResources.
	 */
	public XResources getResources()
	{
		return resources;
	}
	
	/**
	 * @param appResources The appResources to set.
	 */
	public void setResources(XResources appResources)
	{
		this.resources = appResources;
	}
	
	protected void setupLookAndFeel()
	{
		try
		{
			String laf = UIManager.getSystemLookAndFeelClassName();
			
			if (laf.indexOf(".Metal") != -1 || laf.indexOf(".Motif") != -1)
			{
				// Some JVMs on Linux report metal as their system look and feels.
				// We should avoid metal like the plague.
				
				String replacementLAF = "com.jgoodies.looks.windows.PlasticLookAndFeel"; // Plastic3DLookAndFeel, PlasticXPLookAndFeel
				
				if (XUtils.classForName(replacementLAF) != null)
				{
					// TODO: this might be necessary for WebStart deployments.
					// UIManager.put("ClassLoader", LookUtils.class.getClassLoader());
					laf = replacementLAF;
				}
			}
			
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		}
		catch (Throwable t)
		{
			log.error("unhandled exception", t);
		}
	}
	
	/**
	 * Returns the I18N string for the given key.
	 * 
	 * @param key
	 * @return the I18N string for the given key, or the key itself if no I18N string was found
	 */
	public String getString(String key)
	{
		return getResources().getString(key);
	}
	
	/**
	 * 
	 * 
	 * @param throwable
	 */
	public void unhandledException(Throwable throwable)
	{
		// log it
		log.error("unhandled exception", throwable);
		
		// display it in a dialog 
		XErrorDialog.showError("Unhandled Exception", throwable);
	}
	
	public boolean getExitOnLastWindowClose()
	{
		return exitOnLastWindowClose;
	}
	
	public void setExitOnLastWindowClose(boolean exitOnLastWindowClose)
	{
		this.exitOnLastWindowClose = exitOnLastWindowClose;
	}
	
	/**
	 * @return an action that when performed will attempt to shut down the application
	 */
	public Action getApplicationExitAction()
	{
		return new AbstractAction() {
			public void actionPerformed(ActionEvent event) {
				shutdownApplication();
			}
		};
	}
	
	/**
	 * @param command
	 * @param action
	 * @return whether the registration was successful
	 */
	public boolean registerGlobalAction(String command, Action action)
	{
		XProxyAction defaultAction = getResources().getAction(command);
		
		if (defaultAction == null)
			return false;
		
		defaultAction.setActionProxy(action);
		
		return true;
	}
	
	/**
	 * @return Returns the name.
	 */
	public String getAppName()
	{
		return appName;
	}

	/**
	 * @param name The name to set.
	 */
	public void setAppName(String name)
	{
		this.appName = name;
	}
	

    /**
	 * Returns the icon for this application.
	 * 
	 * @return Returns the icon for this application.
	 */
	public ImageIcon getAppIcon()
	{
		return appIcon;
	}

	/**
	 * Set the icon for this application. The icon is used as the default icon
	 * for XAppFrames.
	 * 
	 * @param appIcon
	 */
	public void setAppIcon(ImageIcon appIcon)
	{
		this.appIcon = appIcon;
	}

	/**
	 * Method restoreState.
	 * @param preferences Preferences
	 */
	protected void restoreState(Preferences preferences)
	{
		
	}
	
	/**
	 * Method saveState.
	 * @param preferences Preferences
	 */
	protected void saveState(Preferences preferences)
	{
		
	}
	
	/**
	 * @return the Preferences object for this application
	 */
	public Preferences getAppPreferences()
	{
		if (appPrefs == null)
		{
			appPrefs = Preferences.userNodeForPackage(getClass());
			
			appPrefs = appPrefs.node(XUtils.getShortName(getClass()).toLowerCase());
			
			if (getAppName() != null)
				appPrefs = appPrefs.node(getAppName());
		}
		
		return appPrefs;
	}
	
	/**
	 * @param clazz
	 * @return the Preferences object for the given class
	 */
	public Preferences getPreferences(Class clazz)
	{
		return getPreferences(clazz, null);
	}
	
	/**
	 * @param clazz
	 * @param id a String which uniquely identifies a Preferences object within the set
	 *           of Preferences for a class
	 * @return the Preferences object for the given class and id
	 */
	public Preferences getPreferences(Class clazz, String id)
	{
		String key = clazz.getName().toLowerCase().replace('.', '_');
		
		if (id != null && id.length() > 0)
			key += "-" + id;
		
		return getAppPreferences().node(key);
	}
	
	
	/**
	 * @param frame
	 */
	public void register(XFrame frame)
	{
		frames.add(frame);
	}
	
	
	/**
	 * @param frame
	 * @return whether the frame was being tracked by this application
	 */
	public boolean unregister(XFrame frame)
	{
		boolean wasRegistered = frames.remove(frame);
		
		if (!wasRegistered)
			return false;
		
		if (frames.size() == 0 && getExitOnLastWindowClose())
		{
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					getApplicationExitAction().actionPerformed(null);
				}
			});
		}
		
		return true;
	}
	
	/**
	 * Shut the application down. This will ask to close dirty frames, save the
	 * application state / preferences, and exit the VM.
	 */
	public void shutdownApplication()
	{
		// Ask all the windows whether it's ok.
		Iterator windows = new ArrayList(frames).iterator();
		
		while (windows.hasNext())
		{
			XFrame frame = (XFrame)windows.next();
			
			if (!frame.canCloseFrame())
				return;
		}
		
		// Save the application state, including child frames' state.
		saveState(getAppPreferences());
		
		try
		{
			getAppPreferences().flush();
		}
		catch (BackingStoreException bse)
		{
			log.warn("Error saving application preferences", bse);
		}
		
		// Exit!
		System.exit(0);
	}
	
	private static boolean platformInited;
	
	private static void platformInit(XApp app)
    {
		if (platformInited)
			return;
		
		// The XPlatform.platformInit() method is called by reflection so that it doesn't
		// have to be made public and clutter the public API.
		XReflectClass platformClass = XReflectClass.createReflectClass(XPlatform.class);
		
		platformClass.invokeStatic("platformInit", app);
		
		platformInited = true;
    }
    
}
